/**
 * @file
 * @brief This file contains the slog2 context abstraction for the logging API.
 */

#ifndef NOSAL_LOG_LOGGER_H
#define NOSAL_LOG_LOGGER_H

#include "nosal/log/common.h"
#include "nosal/log/log_stream.h"
#include "nosal/log/internal/log_manager.h"
#include <memory>
#include <atomic>

namespace netaos {
namespace nosal {
namespace log {

/**
 * @defgroup logging Logging API
 * @brief The high-level logging API.
 *
 * Accumulation of function and utilities for using the logging framework.
 */

/// @addtogroup logging
/// @{
/* -------- High-level API -------- */

/**
 * @brief  Logs decimal numbers in hexadecimal format.
 *
 * Negatives are represented in 2's complement. The number of represented digits depends on the
 * overloaded parameter type length.
 *
 * @code
 * uint16_t num = 42;
 * LogInfo() << "This is some number in hex:" << HexFormat(num);
 * LogInfo() << "This is some negative number in hex:" << HexFormat(-num);
 * @endcode
 *
 * @param[in] value Decimal number to be represented in 8 digits
 * @return          @c LogHex8 type that has a built-in stream handler
 */
constexpr LogHex8 HexFormat(uint8_t value) noexcept
{
    return LogHex8{value};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 8 digits
 * @return          @c LogHex8 type that has a built-in stream handler
 */
constexpr LogHex8 HexFormat(int8_t value) noexcept
{
    return LogHex8{static_cast<uint8_t>(value)};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 16 digits
 * @return          @c LogHex16 type that has a built-in stream handler
 */
constexpr LogHex16 HexFormat(uint16_t value) noexcept
{
    return LogHex16{value};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 16 digits
 * @return          @c LogHex16 type that has a built-in stream handler
 */
constexpr LogHex16 HexFormat(int16_t value) noexcept
{
    return LogHex16{static_cast<uint16_t>(value)};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 32 digits
 * @return          @c LogHex32 type that has a built-in stream handler
 */
constexpr LogHex32 HexFormat(uint32_t value) noexcept
{
    return LogHex32{value};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 32 digits
 * @return          @c LogHex32 type that has a built-in stream handler
 */
constexpr LogHex32 HexFormat(int32_t value) noexcept
{
    return LogHex32{static_cast<uint32_t>(value)};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 64 digits
 * @return          @c LogHex64 type that has a built-in stream handler
 */
constexpr LogHex64 HexFormat(uint64_t value) noexcept
{
    return LogHex64{value};
}

/**
 * @brief Same as @c HexFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 64 digits
 * @return          @c LogHex64 type that has a built-in stream handler
 */
constexpr LogHex64 HexFormat(int64_t value) noexcept
{
    return LogHex64{static_cast<uint64_t>(value)};
}

/**
 * @brief Logs decimal numbers in binary format.
 *
 * Negatives are represented in 2's complement. The number of represented digits depends on the
 * overloaded parameter type length.
 *
 * @code
 * LogInfo() << "This is some number in bin:" << BinFormat(42);
 * LogInfo() << "This is some negative number in bin:" << BinFormat(-42);
 * @endcode
 *
 * @param[in] value Decimal number to be represented in 8 digits
 * @return          @c LogBin8 type that has a built-in stream handler
 */
constexpr LogBin8 BinFormat(uint8_t value) noexcept
{
    return LogBin8{value};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 8 digits
 * @return          @c LogBin8 type that has a built-in stream handler
 */
constexpr LogBin8 BinFormat(int8_t value) noexcept
{
    return LogBin8{static_cast<uint8_t>(value)};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 16 digits
 * @return          @c LogBin16 type that has a built-in stream handler
 */
constexpr LogBin16 BinFormat(uint16_t value) noexcept
{
    return LogBin16{value};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 16 digits
 * @return          @c LogBin16 type that has a built-in stream handler
 */
constexpr LogBin16 BinFormat(int16_t value) noexcept
{
    return LogBin16{static_cast<uint16_t>(value)};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 32 digits
 * @return          @c LogBin32 type that has a built-in stream handler
 */
constexpr LogBin32 BinFormat(uint32_t value) noexcept
{
    return LogBin32{value};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 32 digits
 * @return          @c LogBin32 type that has a built-in stream handler
 */
constexpr LogBin32 BinFormat(int32_t value) noexcept
{
    return LogBin32{static_cast<uint32_t>(value)};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 64 digits
 * @return          @c LogBin64 type that has a built-in stream handler
 */
constexpr LogBin64 BinFormat(uint64_t value) noexcept
{
    return LogBin64{value};
}

/**
 * @brief Same as @c BinFormat(uint8_t value).
 *
 * @param[in] value Decimal number to be represented in 64 digits
 * @return          @c LogBin64 type that has a built-in stream handler
 */
constexpr LogBin64 BinFormat(int64_t value) noexcept
{
    return LogBin64{static_cast<uint64_t>(value)};
}


/* -------- END of API -------- */
/// @}

/**
 * @ingroup logger
 * @class Logger
 *
 * @brief Class representing a slog2 logger context.
 *
 * slog2 defines so called _contexts_ which can be seen as logger instances within one application
 * or process scope. Contexts have the same properties to be set up as for the application:
 *  -# ID (_up to four-character_)
 *  -# description text (opt.)
 *  -# default log severity level
 *
 * A context will be automatically registered against the slog2 back-end during creation phase, as well
 * as automatically deregistered during process shutdown phase. So the end user does not care for the
 * objects life time.
 */
class Logger final
{
public:
    /**
     * @brief Creates a @c LogStream object.
     *
     * Returned object will accept arguments via the _insert stream operator_ "@c <<".
     *
     * @note In the normal usage scenario, the object's life time of the created @c LogStream is scoped
     * within one statement (ends with @c ; after last passed argument). If one wants to extend the
     * @c LogStream object's life time, the object might be assigned to a named variable.
     * @see Refer to the @c LogStream class documentation for further details.
     *
     * @return  @c LogStream object of Fatal severity.
     */
    LogStream LogFatal() const noexcept;
    /**
     * @brief Same as @c Logger::LogFatal().
     * @return  @c LogStream object of Error severity.
     */
    LogStream LogError() const noexcept;
    /**
     * @brief Same as @c Logger::LogFatal().
     * @return  @c LogStream object of Warn severity.
     */
    LogStream LogWarn() const noexcept;
    /**
     * @brief Same as @c Logger::LogFatal().
     * @return  @c LogStream object of Info severity.
     */
    LogStream LogInfo() const noexcept;
    /**
     * @brief Same as @c Logger::LogFatal().
     * @return  @c LogStream object of Debug severity.
     */
    LogStream LogDebug() const noexcept;
    /**
     * @brief Same as @c Logger::LogFatal().
     * @return  @c LogStream object of Verbose severity.
     */
    LogStream LogVerbose() const noexcept;
    /**
     * @brief Log message with a programmatically determined log level can be written.
     * @param logLevel the log level to use for this LogStream instance
     * @return a new LogStream instance with the given log level
     */
    LogStream WithLevel(LogLevel logLevel) const noexcept;
    /**
     * @brief Check current configured log reporting level.
     *
     * Applications may want to check the actual configured reporting log level of certain loggers
     * before doing log data preparation that is runtime intensive.
     *
     * @param[in] logLevel  The to be checked log level.
     * @return              True if desired log level satisfies the configured reporting level.
     */
    bool IsEnabled(LogLevel logLevel) const noexcept;

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    Logger(Logger&&) = delete;
    Logger& operator=(Logger&&) = delete;

private:
    /**
     * @brief Creates a @c Logger object.
     *
     * Holds the slog2 context which is registered in the slog2 back-end.
     */
    Logger(std::string context_id, std::string description, LogLevel default_loglevel);

    /**
     * @brief Deregisters the slog2 context from the slog2 back-end.
     */
    ~Logger();

    friend class internal::LogManager;
    friend LogStream::LogStream(LogLevel, const Logger&);

    void* getContext() const noexcept;
    void deinitialize() noexcept;
    void enableConsole(bool enable) noexcept;
    void enableRemote(bool enable) noexcept;

    std::atomic<bool> is_initialized_{false};

    std::string context_id_;
    std::string description_; 
    LogLevel default_loglevel_;
    void* logcontext_;
};

/**
 * @brief Creates wrapper object with provided name and unit attributes for
 *        all arithmetic types except bool, including their reference types
 */
template <typename T,
    typename
    = std::enable_if_t<std::is_arithmetic<std::decay_t<T>>::value and not std::is_same<std::decay_t<T>, bool>::value>>
Argument<T> Arg(T&& arg, const std::string& name, const std::string& unit, Format format = Dflt()) noexcept
{
    return {std::forward<T>(arg), name, unit, format};
}

/**
 * @brief Creates wrapper object with provided name attribute for
 *        all arithmetic types including their reference types,
 *        types convertible to nosal::core::StringView
 *        and types convertible to nosal::core::Span
 */
template <typename T,
    typename = std::enable_if_t<std::is_arithmetic<std::decay_t<T>>::value
        or std::is_convertible<T, std::string>::value>>
Argument<T> Arg(T&& arg, const std::string& name) noexcept
{
    return {std::forward<T>(arg), name, std::string(), Dflt()};
}

/**
 * @brief Creates wrapper object without attributes for
 *        all arithmetic types including their reference types,
 *        types convertible to nosal::core::StringView
 *        and types convertible to nosal::core::Span
 */
template <typename T,
    typename = std::enable_if_t<std::is_arithmetic<std::decay_t<T>>::value
        or std::is_convertible<T, std::string>::value>>
Argument<T> Arg(T&& arg) noexcept
{
    return {std::forward<T>(arg), std::string(), std::string(), Dflt()};
}

} /* namespace log */
} /* namespace nosal */
} /* namespace netaos */

#endif  // NOSAL_LOG_LOGGER_H
